using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

namespace XnaInForm
{
    public partial class XnaForm : Form
    {
        class GfxService : IGraphicsDeviceService
        {
            GraphicsDevice gfxDevice;

            public GfxService(GraphicsDevice gfxDevice)
            {
                this.gfxDevice = gfxDevice;
                DeviceCreated = new EventHandler(DoNothing);
                DeviceDisposing = new EventHandler(DoNothing);
                DeviceReset = new EventHandler(DoNothing);
                DeviceResetting = new EventHandler(DoNothing);
            }

            public GraphicsDevice GraphicsDevice
            { get { return gfxDevice; } }

            public event EventHandler DeviceCreated;
            public event EventHandler DeviceDisposing;
            public event EventHandler DeviceReset;
            public event EventHandler DeviceResetting;
            
            void DoNothing(object o, EventArgs args)
            {
            }
        }

        GraphicsDevice gfxDevice;
        ContentManager contentMgr;
        DepthStencilBuffer defaultDepthStencil;
        SpriteBatch copySprite;

        SpriteBatch smallViewSprite;
        Texture2D frontTexture, backTexture;
        VertexPositionNormalTexture[] quad;
        Effect doubleSidedFx;

        Model tiger;
        Matrix projectionMatrix = Matrix.CreatePerspective(0.577f * 2.0f, 0.433f * 2.0f, 1.0f, 1000.0f);
        Matrix worldMatrix = Matrix.Identity;
        float yAngle = 0;

        bool wireframe = false;
        bool wKeyPressed = false;
        long lastTimeCount = 0;

        public XnaForm()
        {
            InitializeComponent();

            CreateDevice();
            copySprite = new SpriteBatch(gfxDevice);
            defaultDepthStencil = gfxDevice.DepthStencilBuffer;

            bigView.Initialize(gfxDevice, new XnaView.PaintFunction(Blit), new Vector4(0.5f, 0.5f, 1.0f, 1.0f));

            // initialize the content manager
            GfxService gfxService = new GfxService(gfxDevice);
            GameServiceContainer services = new GameServiceContainer();
            services.AddService(typeof(IGraphicsDeviceService), gfxService);
            contentMgr = new ContentManager(services);

            // use it to load a couple of texture and a model
            frontTexture = contentMgr.Load<Texture2D>("content/front");
            backTexture = contentMgr.Load<Texture2D>("content/back");
            smallViewSprite = new SpriteBatch(gfxDevice);

            tiger = contentMgr.Load<Model>("content/tiger");
            worldMatrix.Translation = new Vector3(0, 0, -3);

            // simple quad
            quad = new VertexPositionNormalTexture[4];
            Vector3 quadNormal = new Vector3(0, 0, 1);

            quad[0].Position = new Vector3(-1, 1, 0);
            quad[0].Normal = quadNormal;
            quad[0].TextureCoordinate = new Vector2(0, 0);

            quad[1].Position = new Vector3(-1, -1, 0);
            quad[1].Normal = quadNormal;
            quad[1].TextureCoordinate = new Vector2(0, 1);

            quad[2].Position = new Vector3(1, -1, 0);
            quad[2].Normal = quadNormal;
            quad[2].TextureCoordinate = new Vector2(1, 1);

            quad[3].Position = new Vector3(1, 1, 0);
            quad[3].Normal = quadNormal;
            quad[3].TextureCoordinate = new Vector2(1, 0);

            // custom effect
            doubleSidedFx = contentMgr.Load<Effect>("content/doubleSided");

            doubleSidedFx.Parameters["Ambient"].SetValue(0.25f);
            doubleSidedFx.Parameters["MatDiffuse"].SetValue(1);

            doubleSidedFx.Parameters["LightDiffuse1"].SetValue(new Vector3(0, 0, 1));
            doubleSidedFx.Parameters["LightDir1"].SetValue(new Vector3(0, 0, -1));
            doubleSidedFx.Parameters["LightDiffuse2"].SetValue(new Vector3(1, 0, 0));
            doubleSidedFx.Parameters["LightDir2"].SetValue(new Vector3(0, 0, 1));

            doubleSidedFx.Parameters["frontTex"].SetValue(frontTexture);
            doubleSidedFx.Parameters["backTex"].SetValue(backTexture);

            // attach game loop
            Application.Idle += new EventHandler(Application_Idle);
            lastTimeCount = PerformanceCounter.QueryPerformanceCounter();
        }

        private void CreateDevice()
        {
            PresentationParameters presentation = new PresentationParameters();
            presentation.AutoDepthStencilFormat = DepthFormat.Depth24;
            presentation.BackBufferCount = 1;
            presentation.BackBufferFormat = SurfaceFormat.Color;
            presentation.BackBufferWidth = 640;
            presentation.BackBufferHeight = 480;
            presentation.DeviceWindowHandle = this.Handle;
            presentation.EnableAutoDepthStencil = true;
            presentation.FullScreenRefreshRateInHz = 0;
            presentation.IsFullScreen = false;
            presentation.MultiSampleQuality = 0;
            presentation.MultiSampleType = MultiSampleType.None;
            presentation.PresentationInterval = PresentInterval.One;
            presentation.PresentOptions = PresentOptions.None;
            presentation.SwapEffect = SwapEffect.Discard;

            gfxDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle,
                CreateOptions.HardwareVertexProcessing, presentation);
        }

        private void Blit(XnaView viewCtrl)
        {
            if(viewCtrl.Texture == null)
            {
                // uncomment the following line to get rid of the red first frame
                // return; 

                ClearOptions options = ClearOptions.Target | ClearOptions.DepthBuffer;
                Vector4 clearColor = new Vector4(1, 0, 0, 1);
                float depth = 1;
                int stencil = 128;
                gfxDevice.Clear(options, clearColor, depth, stencil);
            }

            else
            {
                // copy to back buffer

                gfxDevice.SetRenderTarget(0, null);
                gfxDevice.DepthStencilBuffer = defaultDepthStencil;

                gfxDevice.RenderState.FillMode = FillMode.Solid;

                copySprite.Begin();
                copySprite.Draw(viewCtrl.Texture, Vector2.Zero, Microsoft.Xna.Framework.Graphics.Color.White);
                copySprite.End();

                gfxDevice.SetRenderTarget(0, viewCtrl.RenderTarget);
                gfxDevice.DepthStencilBuffer = viewCtrl.DepthStencil;
            }

            gfxDevice.Present(viewCtrl.ClientArea, null, viewCtrl.Handle);
        }

        private delegate void DrawView();

        private void RenderToTexture(XnaView viewCtrl, DrawView drawFunction)
        {
            gfxDevice.SetRenderTarget(0, viewCtrl.RenderTarget);
            gfxDevice.DepthStencilBuffer = viewCtrl.DepthStencil;

            ClearOptions options = ClearOptions.Target | ClearOptions.DepthBuffer;
            Vector4 clearColor = viewCtrl.ClearColor;
            float depth = 1;
            int stencil = 128;
            gfxDevice.Clear(options, clearColor, depth, stencil);

            // Draw some stuff HERE
            drawFunction();

            gfxDevice.ResolveRenderTarget(0);
            viewCtrl.Texture = viewCtrl.RenderTarget.GetTexture();
        }

        private void DrawBigView()
        {
            gfxDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            gfxDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            foreach(ModelMesh modelMesh in tiger.Meshes)
            {
                foreach(BasicEffect effect in modelMesh.Effects)
                {
                    effect.World = worldMatrix;
                    effect.Projection = projectionMatrix;
                    modelMesh.Draw();
                }
            }
        }

        private void DrawSmallView()
        {
            gfxDevice.VertexDeclaration = new VertexDeclaration(gfxDevice, VertexPositionNormalTexture.VertexElements);
            Matrix wvp = worldMatrix*projectionMatrix;
            doubleSidedFx.Parameters["WorldViewProj"].SetValue(Matrix.Transpose(wvp));

            doubleSidedFx.Begin();
            foreach(EffectPass pass in doubleSidedFx.CurrentTechnique.Passes)
            {
                pass.Begin();
                gfxDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleFan, quad, 0, 2);
                pass.End();
            }
            doubleSidedFx.End();

            // 2 sprites showing the textures used

            Rectangle spriteRect;
            spriteRect.Width = frontTexture.Width/2;
            spriteRect.Height = frontTexture.Height/2;

            smallViewSprite.Begin();
            //smallViewSprite.Draw(backTexture, spriteRect, Color.White);
            spriteRect.X = 0;
            //smallViewSprite.Draw(frontTexture, spriteRect, Color.White);
            smallViewSprite.End();
        }

        private void Draw()
        {
            gfxDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            gfxDevice.RenderState.DepthBufferEnable = true;
            gfxDevice.RenderState.DepthBufferFunction = CompareFunction.LessEqual;
            gfxDevice.RenderState.DepthBufferWriteEnable = true;
            gfxDevice.RenderState.FillMode = wireframe ? FillMode.WireFrame : FillMode.Solid;

            RenderToTexture(bigView, DrawBigView);
            

            Blit(bigView);
            
        }

        private void Update(float deltaTime)
        {
            // Allows the default game to exit on Xbox 360 and Windows
            if(GamePad.GetState(PlayerIndex.One).Buttons.Back == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
                Application.Exit();

            // ESC => exit
            KeyboardState kbState = Keyboard.GetState();
            if(kbState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape))
                Application.Exit();

            // W = toggle wireframe
            if(kbState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.W))
            {
                if(!wKeyPressed)
                {
                    wireframe = !wireframe;
                    wKeyPressed = true;
                }
            }
            else
                wKeyPressed = false;

            // rotate tiger
            yAngle += 0.5f * deltaTime;
            if(yAngle >= 2*Math.PI)
                yAngle -= (float)(2*Math.PI);

            float cos = (float)Math.Cos(yAngle);
            float sin = (float)Math.Sin(yAngle);
            worldMatrix.Right = new Vector3(cos, 0, -sin);
            worldMatrix.Backward = new Vector3(sin, 0, cos);
        }

        //
        // Game Loop stuff
        // from http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx
        //
        [StructLayout(LayoutKind.Sequential)]
        public struct Message
        {
            public IntPtr hWnd;
            public Int32 msg; // was WindowMessage
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public System.Drawing.Point p;
        }

        [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
        [DllImport("User32.dll", CharSet=CharSet.Auto)]
        public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

        void Application_Idle(object sender, EventArgs e)
        {
            while(AppStillIdle)
            {
                long newCount = PerformanceCounter.QueryPerformanceCounter();
                long elapsedCount = newCount - lastTimeCount;
                double elapsedSeconds = (double)elapsedCount / PerformanceCounter.QueryPerformanceFrequency();
                lastTimeCount = newCount;

                Update((float)elapsedSeconds);
                Draw();
            }
        }

        protected bool AppStillIdle
        {
            get
            {
                /*NativeMethods.*/
                Message msg;
                return !/*NativeMethods.*/PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
            }
        }
        //
        // end Game Loop stuff
        //

        private void someDialog_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Hello World!");
            lastTimeCount = PerformanceCounter.QueryPerformanceCounter();
        }
    }
}
